/*
 *    Copyright (c) 2018-2025, edu All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the glng.net developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: glng
 */

package com.zhang.zxx.boot.common.exception;


import com.zhang.zxx.boot.common.base.Response;
import com.zhang.zxx.boot.common.core.SpringContextHolder;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

/**
 * GlobalExceptionHandlerResolver:全局异常处理器
 *
 * @author zhangxiaoxiang
 * @date 2021年12月3日
 */
@Slf4j
@RestControllerAdvice
@SuppressWarnings("all")
public class GlobalExceptionHandlerResolver {


    /**
     * 全局异常
     *
     * @param e the e
     * @return R
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Response<Object> handleGlobalException(Exception e) {

        Response<Object> failed = Response.failed();
        // DEBUG模式返回错误信息
        if (SpringContextHolder.isDebug()) {
            failed.setCustomData(e.getMessage());
            failed.setErrorStackTrace(e.getStackTrace());
        } else {
            failed.setMessage("未知错误，请联系管理员处理");
        }

        // 打印日志并返回
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        String format = apiOperationFormat(request.getRequestURI() + "\n出现全局异常 => {}");
        log.error(format, e.getMessage(), e);
        return failed;
    }

    /**
     * 拒绝时抛出的检查异常
     *
     * @param e e
     * @return Response
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Response<Object> handleAccessDeniedException(AccessDeniedException e) {
        Response<Object> failed = Response.failed();
        log.error("拒绝授权异常信息 ex={}", e.getMessage(), e);
        return failed;
    }

    /**
     * 方法参数校验异常
     *
     * @param e MethodArgumentNotValidException
     * @return Response
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Response<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {

        // 拼装错误信息
        StringJoiner sj = new StringJoiner(",\n", "\n{", "\n}");
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        for (FieldError error : fieldErrors) {
            sj.add(error.getField() + ":" + error.getDefaultMessage());
        }
        // 打印日志并返回
        String message = sj.toString();
        String format = apiOperationFormat("参数校验失败 => {}");
        log.info(format, message);
        return Response.failed().setMessage(message);
    }

    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.OK)
    public Response<Object> handleIllegalArgumentException(IllegalArgumentException e) {

        // 打印日志并返回
        String message = e.getMessage();
        String format = apiOperationFormat("出现数据异常 => {}");
        log.info(format, message);
        log.error("", e);
        return Response.failed().setMessage(message);
    }

    /**
     * 统一处理请求参数校验(实体对象传参)
     *
     * @param e BindException
     * @return Response
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Response<Object> handlerBindException(BindException e) {

        // 拼装错误信息
        StringJoiner sj = new StringJoiner(",\n", "\n{", "\n}");
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        for (FieldError error : fieldErrors) {
            sj.add(error.getField() + ":" + error.getDefaultMessage());
        }
        // 打印日志并返回
        String message = sj.toString();
        String format = apiOperationFormat("参数校验失败 => {}");
        log.info(format, message);
        return Response.failed().setMessage(message);
    }

    /**
     * 业务异常处理
     *
     * @param exception BusinessException
     * @returnResponse
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    public Response<Object> handlerBusinessException(BusinessException exception) {

        Response<Object> failed = Response.failed();
        failed.setMessage(exception.getMessage());
        failed.setStatusCode(exception.getEMsgCode());
        if (SpringContextHolder.isDebug()) {
            // 检查DebugData
            failed.setCustomData(Objects.isNull(exception.getDebugData()) ? exception.getCause() : exception.getDebugData());
        }
        // 打印日志并返回
        String format = apiOperationFormat("出现业务异常 => {}");
        if (exception.isPrintDetail()) {
            log.error(format, exception);
        } else {
            log.info(apiOperationFormat("出现业务异常 => {}") + " => " + exception.getMessage());
            log.debug(format, exception);
        }
        return failed;
    }

    /**
     * 检查异常处理
     *
     * @param exception CheckedException
     * @returnResponse
     */
    @ExceptionHandler(CheckedException.class)
    @ResponseStatus(HttpStatus.OK)
    public Response<Object> handlerCheckedException(CheckedException exception) {

        Response<Object> failed = Response.failed();
        failed.setMessage(exception.getMessage());
        if (SpringContextHolder.isDebug()) {
            // 检查DebugData
            failed.setCustomData(exception.getMessage());
        }
        // 打印日志并返回
        String format = apiOperationFormat("出现检查异常 => {}");
        log.info(apiOperationFormat("出现检查异常 => {}") + " => " + exception.getMessage());
        log.debug(format, exception);
        return failed;
    }


    private String apiOperationFormat(String format) {
        Object apiOperation = RequestContextHolder.currentRequestAttributes().getAttribute("apiOperation", RequestAttributes.SCOPE_REQUEST);
        if (Objects.nonNull(apiOperation) && apiOperation instanceof ApiOperation) {
            format = ((ApiOperation) apiOperation).value() + " => " + format;
        }
        return format;
    }

}